/////////////////////////////////////////////////////////////////////////////
// Conversion routines: RGB to HLS (Red-Green-Blue to Hue-Luminosity-Saturation).
// See Microsoft KnowledgeBase article Q29240.

#define HLSMAX 240
#define RGBMAX 255

#define UNDEFINED (HLSMAX * 2 / 3)

WORD HueToRGB(WORD w1, WORD w2, WORD wH);

// Convert RGB to HLS.
void RGBtoHLS(COLORREF crRGB, WORD& wH, WORD& wL, WORD& wS)
{
	// Get R, G, and B out of COLORREF.
	WORD wR(GetRValue(crRGB));
	WORD wG(GetGValue(crRGB));
	WORD wB(GetBValue(crRGB));
	// Calculate "lightness".
	BYTE byMax(static_cast<BYTE>(max(max(wR, wG), wB)));
	BYTE byMin(static_cast<BYTE>(min(min(wR, wG), wB)));
	wL = static_cast<WORD>((((byMax + byMin) * HLSMAX) + RGBMAX) / (2 * RGBMAX));
	// r=g=b --> achromatic case.
	if (byMax == byMin) {
		wS = 0;
		wH = UNDEFINED;
	}else {
		// Saturation.
		if (wL <= (HLSMAX / 2))
			wS = static_cast<WORD>((((byMax - byMin) * HLSMAX) + ((byMax+byMin) / 2)) / (byMax + byMin));
		else
			wS = static_cast<WORD> ((((byMax - byMin) * HLSMAX) + ((2 * RGBMAX - byMax - byMin) / 2)) / (2 * RGBMAX - byMax - byMin));
		// Hue.
		WORD wRdelta(static_cast<WORD>((((byMax - wR) * (HLSMAX / 6)) + ((byMax - byMin) / 2)) / (byMax - byMin)));
		WORD wGdelta(static_cast<WORD>((((byMax - wG) * (HLSMAX / 6)) + ((byMax - byMin) / 2)) / (byMax - byMin)));
		WORD wBdelta(static_cast<WORD> ((((byMax - wB) * (HLSMAX / 6)) + ((byMax - byMin) / 2)) / (byMax - byMin)));
		if (wR == byMax)
			wH = static_cast<WORD>(wBdelta - wGdelta);
		else if (wG == byMax)
			wH = static_cast<WORD> ((HLSMAX / 3) + wRdelta - wBdelta);
		else
			// B == byMax
			wH = static_cast<WORD> (((2 * HLSMAX) / 3) + wGdelta - wRdelta);
		if (wH < 0)
			wH += HLSMAX;
		if (wH > HLSMAX)
			wH -= HLSMAX;
	}
}

// Convert HLS to RGB.
COLORREF HLStoRGB(WORD wH, WORD wL, WORD wS){
	WORD wR(0);
	WORD wG(0);
	WORD wB(0);
	// Achromatic case.
	if (0 == wS) {
		wR = static_cast<WORD> ((wL * RGBMAX) / HLSMAX);
		wG = static_cast<WORD> ((wL * RGBMAX) / HLSMAX);
		wB = static_cast<WORD> ((wL * RGBMAX) / HLSMAX);
		if (UNDEFINED != wH) {
			_ASSERTE(! "ERROR");
		}
	}else {
		// Chromatic case.
		WORD Magic1(0);
		WORD Magic2(0);
		// Set up magic numbers.
		if (wL <= HLSMAX / 2)
			Magic2 = static_cast<WORD> ((wL * (HLSMAX + wS) + (HLSMAX / 2)) / HLSMAX);
		else
			Magic2 = static_cast<WORD> (wL + wS - ((wL * wS) + (HLSMAX / 2)) / HLSMAX);
		Magic1 = static_cast<WORD> (2 * wL - Magic2);
		// Get RGB, change units from HLSMAX to RGBMAX.
		wR = static_cast<WORD> ((HueToRGB(Magic1, Magic2, static_cast<WORD> (wH + (HLSMAX / static_cast<WORD> (3)))) * RGBMAX + (HLSMAX / static_cast<WORD> (2))) / HLSMAX);
		wG = static_cast<WORD> ((HueToRGB(Magic1, Magic2, static_cast<WORD> (wH)) * RGBMAX + (HLSMAX / static_cast<WORD> (2))) / HLSMAX);
		wB = static_cast<WORD> ((HueToRGB(Magic1, Magic2, static_cast<WORD> (wH - (HLSMAX / static_cast<WORD> (3)))) * RGBMAX + (HLSMAX / static_cast<WORD> (2))) / HLSMAX);
	}
	return RGB(wR,wG,wB);
}

// Utility routine for HLStoRGB.
WORD HueToRGB(WORD w1, WORD w2, WORD wH){
	// Range check: note values passed add/subtract thirds of range.
	if (wH < 0)
		wH += HLSMAX;
	if (wH > HLSMAX)
		wH -= HLSMAX;
	// Return r, g, or b value from this tridrant.
	if (wH < HLSMAX / 6)
		return static_cast<WORD> (w1 + (((w2 - w1) * wH + (HLSMAX / 12)) / (HLSMAX / 6)));
	if (wH < HLSMAX / 2)
		return w2;
	if (wH < (HLSMAX * 2) / 3)
		return static_cast<WORD> (w1 + (((w2 - w1) * (((HLSMAX * 2) / 3) - wH) + (HLSMAX / 12)) / (HLSMAX / 6)));
	else
		return w1;
}
